package com.example.springbootmvc.servlet;

import com.example.springbootmvc.annotation.*;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static jdk.nashorn.api.scripting.ScriptUtils.convert;

public class KVDispatcherServlet extends HttpServlet {
    private Map<String, Object> ioc = new HashMap<>();
    private List<String> classNames = new ArrayList<>();
    private Properties properties = new Properties();
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            this.doDispatcher(req, resp);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig config) {
        System.out.println("servlet init 我执行了");
        try {
            //1、加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
            //2、扫描相关的类
            doScanner(properties.getProperty("scanPackage"));
            //3、初始化扫描到的类，并且将它们放入到 ICO 容器之中
            doInstance();
            //4、完成依赖注入
            doAutowired();
            //5、初始化 HandlerMapping
            initHandlerMapping();
            System.out.println("GP Spring framework is init.");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(KVController.class)) {
                String baseUrl = "";
                if (clazz.isAnnotationPresent(KVRequestMapping.class)) {
                    baseUrl = clazz.getAnnotation(KVRequestMapping.class).value().trim();
                }
                for (Method method : clazz.getMethods()) {
                    if (method.isAnnotationPresent(KVRequestMapping.class)) {
                        String url = ("/" + baseUrl + "/" + method.getAnnotation(KVRequestMapping.class).value()).replaceAll("/+", "/");
                        handlerMapping.add(new Handler(Pattern.compile(url), entry.getValue(), method));
                        System.out.println("mapping " + url + "," + method);
                    }
                }
            }
        }
    }

    private void doAutowired() throws IllegalAccessException {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(KVAutowired.class)) {
                    KVAutowired kvAutowired = field.getAnnotation(KVAutowired.class);
                    String baseName = kvAutowired.value();
                    if (StringUtils.isEmpty(baseName)) {
                        baseName = field.getType().getName();
                    }
                    field.setAccessible(true);

                    field.set(entry.getValue(), ioc.get(baseName));
                }
            }
        }

    }

    private void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (classNames.size() == 0) {
            return;
        }
        for (String className : classNames) {
            if (className.contains(".")) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(KVController.class)) {
                    ioc.put(StringUtils.capitalize(clazz.getSimpleName()), clazz.newInstance());
                } else if (clazz.isAnnotationPresent(KVService.class)) {
                    KVService kvService = clazz.getAnnotation(KVService.class);
                    String serviceName = kvService.value();
                    if (StringUtils.isEmpty(serviceName.trim())) {
                        serviceName = StringUtils.uncapitalize(clazz.getSimpleName());
                    }
                    Object object = clazz.newInstance();
                    ioc.put(serviceName, object);

                    for (Class<?> i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName())) {
                            System.out.println("the \"" + i.getName() + "\" is exists!!");
                        }
                        ioc.put(i.getName(), object);
                    }
                }
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) throws IOException {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        properties = new Properties();
        properties.load(inputStream);
    }

    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource( scanPackage.replaceAll("\\.", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (file.getName().endsWith(".class")) {
                    String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                    classNames.add(clazzName);
                }
            }
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
    }

    private void doDispatcher(HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException {
        Handler handler = getHandler(request);
        if (handler == null) {
            response.getWriter().write("404 Not Found!");
            return ;
        }

        //获得方法的形参列表
        Class<?> [] paramTypes = handler.method.getParameterTypes();
        Object [] paramValues = new Object[paramTypes.length];
        Map<String,String[]> params = request.getParameterMap();
        for (Map.Entry<String, String[]> parm : params.entrySet()) {
            String value = Arrays.toString(parm.getValue()).replaceAll("\\[|\\]","")
                    .replaceAll("\\s",",");
            if(!handler.paramIndexMapping.containsKey(parm.getKey())){continue;}
            int index = handler.paramIndexMapping.get(parm.getKey());
            paramValues[index] = convert(paramTypes[index],value);
        }
        if(handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = request;
        }
        if(handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = response;
        }
        Object returnValue = handler.method.invoke(handler.controller,paramValues);
        if(returnValue == null || returnValue instanceof Void){ return; }
        response.getWriter().write(returnValue.toString());

    }
    private Object convert(Class<?> type,String value){
        if(Integer.class == type){
            return Integer.valueOf(value);
        }
        return value;
    }
    private Handler getHandler(HttpServletRequest request) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        String url = request.getRequestURI();
        String contextPath = request.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.pattern.matcher(url);
            if(!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return  null;
    }
}
/**
 * Handler 记录 Controller 中的 RequestMapping 和 Method 的对应关系
 *
 *
 */
class Handler {
    protected Object controller; //保存方法对应的实例
    protected Method method; //保存映射的方法
    protected Pattern pattern;
    protected Map<String, Integer> paramIndexMapping; //参数顺序

    /**
     * 构造一个 Handler 基本的参数
     *
     * @param controller
     * @param method
     */
    protected Handler(Pattern pattern, Object controller, Method method) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        paramIndexMapping = new HashMap<String, Integer>();
        putParamIndexMapping(method);
    }

    private void putParamIndexMapping(Method method) {
//提取方法中加了注解的参数
        Annotation[][] pa = method.getParameterAnnotations();
        for (int i = 0; i < pa.length; i++) {
            for (Annotation a : pa[i]) {
                if (a instanceof KVRequestParam) {
                    String paramName = ((KVRequestParam) a).value();
                    if (!"".equals(paramName.trim())) {
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
        }
//提取方法中的 request 和 response 参数
        Class<?>[] paramsTypes = method.getParameterTypes();
        for (int i = 0; i < paramsTypes.length; i++) {
            Class<?> type = paramsTypes[i];
            if (type == HttpServletRequest.class ||
                    type == HttpServletResponse.class) {
                paramIndexMapping.put(type.getName(), i);
            }
        }
    }
}
